import os
import re
import json
import uuid
import threading
import difflib
import hashlib
from flask import Flask, request, jsonify
from openai import OpenAI
from flask_cors import CORS
import docx  # 用于处理Word文档

app = Flask(__name__)
CORS(app, supports_credentials=True, origins=["*"])


# 线程安全的机器人管理器
class ExamBotManager:
    def __init__(self):
        self.lock = threading.Lock()
        self.bots = {}

    def get_bot(self, session_id):
        with self.lock:
            return self.bots.get(session_id)

    def create_bot(self, session_id, config):
        with self.lock:
            bot = SmartExamBot(
                api_key=config["api_key"],
                base_url=config["base_url"],
                max_questions=config["max_questions"],
                knowledge_files=config["knowledge_files"]
            )
            self.bots[session_id] = bot
            return bot


exam_bot_manager = ExamBotManager()


class SmartExamBot:
    def __init__(self, api_key: str, base_url: str, max_questions: int = 5, knowledge_files: list = None):
        self.client = OpenAI(api_key=api_key, base_url=base_url)
        self.max_questions = max_questions
        self.knowledge_files = knowledge_files or []
        self.generated_questions = set()  # 存储已生成题目的哈希值
        self.reset_exam()

        # 禁止的关键词列表
        self.forbidden_words = {
            '所示', '源程序', '文件', '例如', '单击',
            '进攻', '其中', '防守', '源代码'
        }

    def reset_exam(self):
        """重置考试状态"""
        self.question_count = 0
        self.total_score = 0
        self.question_scores = []
        self.current_question = ""
        self.knowledge_content = ""
        self.reference_answer = ""  # 存储参考答案
        self.generated_questions = set()  # 重置已生成题目集合

    def clean_text(self, text: str) -> str:
        """移除所有禁止的关键词"""
        for word in self.forbidden_words:
            text = text.replace(word, "")
        return text.strip()

    def load_knowledge(self):
        """从多个文件加载知识库内容"""
        full_content = ""

        for file_path in self.knowledge_files:
            if not os.path.exists(file_path):
                print(f"文件不存在: {file_path}")
                continue

            try:
                if file_path.lower().endswith('.docx'):
                    # 处理Word文档
                    doc = docx.Document(file_path)
                    text = "\n".join([para.text for para in doc.paragraphs])
                else:
                    # 处理文本文件
                    with open(file_path, 'r', encoding='utf-8', errors='replace') as f:
                        text = f.read()

                full_content += self.clean_text(text) + "\n\n"
                print(f"已加载文件: {os.path.basename(file_path)}")
            except Exception as e:
                print(f"加载文件失败 {file_path}: {str(e)}")

        self.knowledge_content = full_content
        print(f"知识库总字数: {len(self.knowledge_content)}")
        return self.knowledge_content

    def get_ai_response(self, prompt: str) -> str:
        """获取AI响应并过滤内容"""
        try:
            response = self.client.chat.completions.create(
                model="deepseek-v3",
                messages=[{"role": "user", "content": prompt}],
                temperature=0.2
            ).choices[0].message.content
            return self.clean_text(response)
        except Exception as e:
            print(f"API请求失败: {str(e)}")
            return "无法生成响应，请稍后再试"

    def generate_question(self) -> str:
        """生成纯净且不重复的题目"""
        if self.question_count >= self.max_questions:
            return None

        # 加载知识库（如果尚未加载）
        if not self.knowledge_content:
            self.load_knowledge()

        # 尝试生成新题目（最多尝试5次）
        for attempt in range(5):
            prompt = f"""
            根据以下知识库生成一个考试题目：
            {self.knowledge_content[:3000]}

            要求：
            1. 必须是需要分析阐述的主观题
            2. 只返回题目文本，不要包含任何其他内容
            3. 不要包含任何示例、评分标准或解释
            4. 生成的题目难度较低
            4. 确保题目与已生成题目不同（已生成题目: {list(self.generated_questions)[:3] if self.generated_questions else '无'}）
            """

            question = self.get_ai_response(prompt)

            # 检查题目是否有效且不重复
            if question and len(question) > 10:
                # 生成题目哈希值用于去重
                question_hash = hashlib.md5(question.encode()).hexdigest()

                if question_hash not in self.generated_questions:
                    self.current_question = question
                    self.generated_questions.add(question_hash)
                    self.question_count += 1
                    return question

            print(f"题目生成尝试 {attempt + 1}/5: 无效或重复题目")

        return None  # 生成失败

    def is_copied_question(self, user_answer: str) -> bool:
        """检查用户是否直接复制题目"""
        if not self.current_question or not user_answer:
            return False

        # 计算题目和答案的相似度
        similarity = difflib.SequenceMatcher(
            None,
            self.current_question.lower(),
            user_answer.lower()
        ).ratio()

        return similarity > 0.8  # 相似度超过80%视为复制题目

    def generate_reference_answer(self) -> str:
        """生成当前题目的参考答案"""
        if not self.current_question:
            return ""

        prompt = f"""
        根据以下题目和知识库生成标准答案：
        题目：{self.current_question}
        知识库：{self.knowledge_content[:2000]}

        要求：
        1. 只返回答案内容
        2. 分点列出关键要素
        3. 不要包含评分信息
        """
        self.reference_answer = self.get_ai_response(prompt)
        return self.reference_answer

    def evaluate_answer(self, user_answer: str) -> dict:
        """评估用户答案并评分"""
        # 0. 检查是否复制题目
        if self.is_copied_question(user_answer):
            return {
                "score": 0,
                "feedback": "答案与题目相似度过高，可能直接复制题目内容",
                "reference": ""
            }

        # 1. 生成参考答案
        reference = self.generate_reference_answer()

        # 2. 获取评分
        prompt = f"""
        请比较以下答案与参考答案的相似度并评分（0-10分）：
        题目：{self.current_question}
        参考答案：{reference}
        用户答案：{user_answer}

        评分标准：
        10分：答案与参考答案高度一致，包含所有关键点
        8-9分：答案包含大部分关键点且逻辑性很强，有少量遗漏 
        6-7分：答案包含部分关键点且逻辑性较强，但存在重要遗漏
        4-5分：答案仅涉及少量关键点且逻辑性一般，相关性较弱
        0-3分：答案与参考答案基本无关且无逻辑性

        只需返回：分数|简要说明
        示例：8|主要观点匹配但缺少细节
        """

        evaluation = self.get_ai_response(prompt)

        # 3. 解析评分
        try:
            score_str, feedback = evaluation.split("|", 1)
            score = max(0, min(10, int(score_str)))
        except:
            score = 5
            feedback = "评分解析错误，按中等分数处理"

        # 4. 记录分数
        self.total_score += score
        self.question_scores.append(score)

        return {
            "score": score,
            "feedback": feedback,
            "reference": reference,
            "question": self.current_question  # 返回当前题目用于调试
        }


# Flask路由
@app.route('/start_exam', methods=['POST'])
def start_exam():
    """开始考试"""
    session_id = request.cookies.get('session_id') or str(uuid.uuid4())

    # 配置信息
    CONFIG = {
        "api_key": "sk-43f6c93de4ff47ba9cbd7e0dc9ed68b7",
        "base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
        "max_questions": 5,
        # 添加多个知识库文件
        "knowledge_files": [
            r"E:/第3章 数据获取.docx",
            # 可以添加更多文件
            # r"其他文件路径.docx",
        ]
    }

    bot = exam_bot_manager.create_bot(session_id, CONFIG)

    try:
        # 生成第一题
        question = bot.generate_question()

        if not question:
            return jsonify({"error": "题目生成失败"}), 500

        response = jsonify({
            "question": question,
            "question_count": bot.question_count,
            "max_questions": bot.max_questions,
            "knowledge_files": [os.path.basename(f) for f in CONFIG["knowledge_files"]]
        })
        response.set_cookie('session_id', session_id, httponly=True, samesite='None', secure=True)
        return response

    except Exception as e:
        return jsonify({"error": str(e)}), 500


@app.route('/submit_answer', methods=['POST'])
def submit_answer():
    """提交答案"""
    session_id = request.cookies.get('session_id')
    if not session_id:
        return jsonify({"error": "缺少会话ID"}), 400

    bot = exam_bot_manager.get_bot(session_id)
    if not bot:
        return jsonify({"error": "考试未开始"}), 400

    data = request.get_json()
    if not data or "answer" not in data:
        return jsonify({"error": "需要提供答案内容"}), 400

    user_answer = data["answer"]
    result = bot.evaluate_answer(user_answer)

    # 添加总分信息
    result["total_score"] = bot.total_score
    result["average_score"] = bot.total_score / bot.question_count if bot.question_count else 0
    result["question_count"] = bot.question_count
    result["max_questions"] = bot.max_questions

    return jsonify(result)


@app.route('/next_question', methods=['GET'])
def next_question():
    """获取下一题"""
    session_id = request.cookies.get('session_id')
    if not session_id:
        return jsonify({"error": "缺少会话ID"}), 400

    bot = exam_bot_manager.get_bot(session_id)
    if not bot:
        return jsonify({"error": "考试未开始"}), 400

    if bot.question_count >= bot.max_questions:
        # 返回考试完成信息和所有得分
        return jsonify({
            "completed": True,
            "total_score": bot.total_score,
            "max_possible_score": bot.max_questions * 10,
            "question_scores": bot.question_scores
        })

    question = bot.generate_question()
    if not question:
        return jsonify({"error": "题目生成失败"}), 500

    return jsonify({
        "question": question,
        "question_count": bot.question_count,
        "max_questions": bot.max_questions
    })


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)